﻿// Autore: Alessio Parma

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace RecipesManager.Data
{
    [ContractClass(typeof(EntityContract))]
    public interface IEntity
    {
        [Pure]
        int Id { get; }
    }

    [ContractClass(typeof(NamedEntityContract))]
    public interface INamedEntity : IEntity
    {
        [Pure]
        string Name { get; }
    }

    [ContractClass(typeof(EntitiesContract<>))]
    public interface IEntities<out TEntity> : IEnumerable<TEntity>
        where TEntity : class, IEntity
    {
        [Pure]
        int Count { get; }

        [Pure]
        TEntity LastAdded { get; }

        [Pure]
        IEnumerable<TEntity> Unordered { get; }

        [Pure]
        TEntity this[int id] { get; }

        [Pure]
        bool Contains(int id);

        void Remove(int id);
    }

    [ContractClass(typeof(NamedEntitiesContract<>))]
    public interface INamedEntities<out TEntity> : IEntities<TEntity>
        where TEntity : class, INamedEntity
    {
        [Pure]
        TEntity this[string name] { get; }

        [Pure]
        bool Contains(string name);

        void Remove(string name);
    }

    [ContractClass(typeof(LinkContract<>))]
    public interface ILink<out TEntity> : INamedEntities<TEntity>
        where TEntity : class, INamedEntity
    {
        [Pure]
        INamedEntities<TEntity> Entities { get; }

        TEntity Add(string name);
    }

    [ContractClass(typeof(RatingContract<,>))]
    public interface IRating<out TEntity, TRating> : INamedEntity
        where TEntity : class, INamedEntity
        where TRating : struct
    {
        [Pure]
        TEntity Entity { get; }

        [Pure]
        TRating Rating { get; }

        void Update(TRating rating);
    }

    [ContractClass(typeof(RatingsContract<,>))]
    public interface IRatings<out TEntity, TRating> : INamedEntities<IRating<TEntity, TRating>>
        where TEntity : class, INamedEntity
        where TRating : struct
    {
        [Pure]
        INamedEntities<TEntity> Entities { get; }

        IRating<TEntity, TRating> Add(string name, TRating rating);
    }

    [ContractClass(typeof(OtherRatingContract<,>))]
    public interface IOtherRating<out TEntity, TRating>
        where TEntity : class, INamedEntity
        where TRating : struct
    {
        [Pure]
        TEntity Entity { get; }

        [Pure]
        int Id { get; }

        [Pure]
        string Name { get; }

        [Pure]
        TRating Rating { get; }

        void Update(TRating rating);
    }

    [ContractClass(typeof(CategoryContract))]
    public interface ICategory : INamedEntity, IEquatable<ICategory>
    {
        [Pure]
        ILink<ICategory> Children { get; }

        [Pure]
        IEnumerable<IGuest> GuestIntolerances { get; }

        [Pure]
        IEnumerable<IOtherRating<IGuest, FoodRating>> GuestRatings { get; }

        [Pure]
        ILink<IIngredient> Ingredients { get; }

        [Pure]
        ILink<ICategory> Parents { get; }

        void Update(string name);
    }

    [ContractClass(typeof(CategoriesContract))]
    public interface ICategories : INamedEntities<ICategory>
    {
        ICategory Add(string name);
    }

    [ContractClass(typeof(EventContract))]
    public interface IEvent : IEntity, IEquatable<IEvent>
    {
        [Pure]
        DateTime? Date { get; }

        [Pure]
        bool HasEnded { get; }

        [Pure]
        IGuestParticipations Guests { get; }

        [Pure]
        string Name { get; }

        [Pure]
        string Notes { get; }

        [Pure]
        IRecipesServings Recipes { get; }

        void Close();

        void Update(string name, string notes, DateTime? date);
    }

    [ContractClass(typeof(EventsContract))]
    public interface IEvents : IEntities<IEvent>
    {
        IEvent Add(string name, string notes = "", DateTime? date = null);
    }

    [ContractClass(typeof(GuestContract))]
    public interface IGuest : INamedEntity, IEquatable<IGuest>
    {
        [Pure]
        string Notes { get; }

        [Pure]
        ILink<ICategory> CategoryIntolerances { get; }

        [Pure]
        IRatings<ICategory, FoodRating> CategoryRatings { get; }

        [Pure]
        IEnumerable<IEvent> EventsParticipations { get; }

        [Pure]
        ILink<IIngredient> IngredientIntolerances { get; }

        [Pure]
        IRatings<IIngredient, FoodRating> IngredientRatings { get; }

        [Pure]
        IRatings<IGuest, GuestRating> GuestRatings { get; }

        [Pure]
        ILink<IGuest> GuestRelationships { get; }

        [Pure]
        IEnumerable<IOtherRating<IGuest, GuestRating>> OtherGuestRatings { get; }

        [Pure]
        IRatings<IRecipe, FoodRating> RecipeRatings { get; }

        [Pure]
        IRecipeStats RecipeStats { get; }

        void Update(string name, string notes);
    }

    [ContractClass(typeof(GuestsContract))]
    public interface IGuests : INamedEntities<IGuest>
    {
        IGuest Add(string name, string notes = "");
    }

    [ContractClass(typeof(GuestParticipationContract))]
    public interface IGuestParticipation : INamedEntity
    {
        [Pure]
        IGuest Guest { get; }

        [Pure]
        bool HasParticipated { get; }

        void Update(bool partecipated);
    }

    [ContractClass(typeof(GuestParticipationsContract))]
    public interface IGuestParticipations : INamedEntities<IGuestParticipation>
    {
        IGuestParticipation Add(string guestName);
    }

    [ContractClass(typeof(IngredientContract))]
    public interface IIngredient : INamedEntity, IEquatable<IIngredient>
    {
        [Pure]
        ILink<ICategory> Categories { get; }

        [Pure]
        IEnumerable<IGuest> GuestIntolerances { get; }

        [Pure]
        IEnumerable<IOtherRating<IGuest, FoodRating>> GuestRatings { get; }

        [Pure]
        MeasureType MeasureType { get; }

        [Pure]
        IEnumerable<IRecipe> OriginalRecipes { get; }

        [Pure]
        IEnumerable<IRecipe> Recipes { get; }

        [Pure]
        IEnumerable<IOtherRating<IShop, ShopRating>> ShopRatings { get; }

        [Pure]
        IMeasure StockMeasure { get; }

        [Pure]
        double StockQuantity { get; }

        void Update(string name, MeasureType measureType, string stockMeasureName, double stockQuantity);
    }

    [ContractClass(typeof(IngredientsContract))]
    public interface IIngredients : INamedEntities<IIngredient>
    {
        IIngredient Add(string name, MeasureType type, string stockMeasureName = "grammi", double stockQuantity = 0.0);
    }

    [ContractClass(typeof(IngredientQuantityContract))]
    public interface IIngredientQuantity : INamedEntity
    {
        [Pure]
        string FormattedQuantity { get; }

        [Pure]
        IIngredient Ingredient { get; }

        [Pure]
        IMeasure Measure { get; }

        [Pure]
        string Notes { get; }

        [Pure]
        double Quantity { get; }

        void Update(string measureName, double quantity, string notes);
    }

    [ContractClass(typeof(IngredientQuantitiesContract))]
    public interface IIngredientQuantities : INamedEntities<IIngredientQuantity>
    {
        IIngredientQuantity Add(string name, string measureName, double quantity, string notes = "");
    }

    [ContractClass(typeof(MeasureContract))]
    public interface IMeasure : INamedEntity, IEquatable<IMeasure>
    {
        [Pure]
        double ConversionFactor { get; }

        [Pure]
        MeasureFamily Family { get; }

        [Pure]
        string ShortName { get; }

        [Pure]
        MeasureType Type { get; }
    }

    [ContractClass(typeof(MeasuresContract))]
    public interface IMeasures : INamedEntities<IMeasure>
    {
        IMeasure Add(string name, string shortName, MeasureType type, MeasureFamily family, double conversionFactor);
    }

    [ContractClass(typeof(RecipeContract))]
    public interface IRecipe : INamedEntity, IEquatable<IRecipe>
    {
        [Pure]
        IEnumerable<IEvent> Events { get; }

        [Pure]
        IEnumerable<IOtherRating<IGuest, FoodRating>> GuestRatings { get; }

        [Pure]
        bool HasOriginal { get; }

        [Pure]
        IIngredientQuantities Ingredients { get; }

        [Pure]
        MeasureFamily MeasureFamily { get; }

        [Pure]
        IEnumerable<IIngredientQuantity> OriginalIngredients { get; }

        [Pure]
        int? OriginalPrepHours { get; }

        [Pure]
        int? OriginalPrepMinutes { get; }

        [Pure]
        int? OriginalServings { get; }

        [Pure]
        IEnumerable<IRecipeStep> OriginalSteps { get; }

        [Pure]
        int? PrepHours { get; }

        [Pure]
        int? PrepMinutes { get; }

        [Pure]
        int? Servings { get; }

        [Pure]
        string Source { get; }

        [Pure]
        IRecipeSteps Steps { get; }

        [Pure]
        IEnumerable<IRecipe> SubRecipeOf { get; }

        [Pure]
        IRecipeTrials Trials { get; }

        void MakeOriginal();

        void Update(string name, string source, MeasureFamily measureFamily, int? prepHours, int? prepMinutes,
                    int? servings);
    }

    [ContractClass(typeof(RecipesContract))]
    public interface IRecipes : INamedEntities<IRecipe>
    {
        IRecipe Add(string name, string source = "", MeasureFamily measureFamily = MeasureFamily.None,
                    int? prepHours = null, int? prepMinutes = null, int? servings = null);
    }

    [ContractClass(typeof(RecipeServingsContract))]
    public interface IRecipeServings : INamedEntity
    {
        [Pure]
        IRecipe Recipe { get; }

        [Pure]
        int? Servings { get; }

        void Update(int? servings);
    }

    [ContractClass(typeof(RecipesServingsContract))]
    public interface IRecipesServings : INamedEntities<IRecipeServings>
    {
        IRecipeServings Add(string recipeName, int? servings = null);
    }

    [ContractClass(typeof(RecipeStatContract))]
    public interface IRecipeStat : INamedEntity
    {
        [Pure]
        int Count { get; }

        [Pure]
        IRecipe Recipe { get; }

        [Pure]
        DateTime LastServed { get; }

        void Update(DateTime newDate);
    }

    [ContractClass(typeof(RecipeStatsContract))]
    public interface IRecipeStats : INamedEntities<IRecipeStat>
    {
        IRecipeStat Add(string recipeName, DateTime lastServed);
    }

    [ContractClass(typeof(RecipeTrialContract))]
    public interface IRecipeTrial : IEntity
    {
        [Pure]
        string Notes { get; }

        [Pure]
        IRecipe Recipe { get; }
    }

    [ContractClass(typeof(RecipeTrialsContract))]
    public interface IRecipeTrials : IEntities<IRecipeTrial>
    {
        IRecipeTrial Add(string notes);
    }

    [ContractClass(typeof(ShopContract))]
    public interface IShop : INamedEntity, IEquatable<IShop>
    {
        [Pure]
        string Address { get; }

        [Pure]
        string City { get; }

        [Pure]
        string PhoneNumber { get; }

        [Pure]
        string Timetable { get; }

        [Pure]
        IRatings<IIngredient, ShopRating> IngredientRatings { get; }

        void Update(string name, string address, string city, string phoneNumber, string timetable);
    }

    [ContractClass(typeof(ShopsContract))]
    public interface IShops : INamedEntities<IShop>
    {
        IShop Add(string name, string address = "", string city = "", string timetable = "", string phoneNumber = "");
    }

    [ContractClass(typeof(ShoppingListContract))]
    public interface IShoppingList : IEnumerable<IIngredientQuantity>
    {
        [Pure]
        int Count { get; }

        [Pure]
        IIngredientQuantity this[string ingredientName, string measureName] { get; }

        IIngredientQuantity Add(string ingredientName, string measureName, double quantity, string notes = "");

        void Clear();

        [Pure]
        bool Contains(string ingredientName, string measureName);

        void Remove(string ingredientName, string measureName);
    }

    [ContractClass(typeof(RecipeStepContract))]
    public interface IRecipeStep : IEntity
    {
        [Pure]
        string Description { get; }

        [Pure]
        IRecipe Recipe { get; }

        [Pure]
        string Resources { get; }

        [Pure]
        int? Servings { get; }

        [Pure]
        int? StepHours { get; }

        [Pure]
        int? StepMinutes { get; }

        [Pure]
        IRecipe SubRecipe { get; }

        [Pure]
        StepType Type { get; }

        void Update(string description, string resources, int? stepHours, int? stepMinutes);

        void Update(string subRecipeName, int? servings);
    }

    [ContractClass(typeof(RecipeStepsContract))]
    public interface IRecipeSteps : IEntities<IRecipeStep>
    {
        IRecipeStep AddGeneric(string description, string resources = "", int? stepHours = null,
                               int? stepMinutes = null);

        IRecipeStep AddSubRecipe(string subRecipeName, int? servings = null);

        void Clear();
    }

    public enum FoodRating : byte
    {
        VeryBad = 0,
        Bad = 1,
        Neuter = 2,
        Good = 3,
        VeryGood = 4
    }

    public enum GuestRating : byte
    {
        VeryBad = 0,
        Bad = 1,
        Neuter = 2,
        Good = 3,
        VeryGood = 4
    }

    public enum MeasureFamily : byte
    {
        USA = 0,
        UK = 1,
        Metric = 2,
        None = 3
    }

    public enum MeasureType : byte
    {
        Unit = 0,
        Volume = 1,
        Weight = 2,
        None = 3
    }

    public enum ShopRating : byte
    {
        Bad = 0,
        Good = 1
    }

    public enum StepType : byte
    {
        Generic = 0,
        SubRecipe = 1
    }
}